package cn.lynch.bbs.dao;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.Assert;

@Repository
@SuppressWarnings("unchecked")
public class SimpleHibernateDao<T, PK extends Serializable> {

	private Class<T> entityClass;

	@Autowired
	protected HibernateTemplate hibernateTemplate;

	public void save(T entity) {
		Assert.notNull(entity);
		hibernateTemplate.save(entity);
	}

	public void remove(final PK... ids) {
		Assert.notNull(ids);
		Assert.noNullElements(ids);

		hibernateTemplate.execute(new HibernateCallback() {

			@Override
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				String idPropertyName = session.getSessionFactory()
						.getClassMetadata(entityClass)
						.getIdentifierPropertyName();

				session.createQuery(
						"DELETE FROM " + entityClass.getName()
								+ " entity WHERE entity." + idPropertyName
								+ " IN (:idList)").setParameterList("idList",
						Arrays.asList(ids)).executeUpdate();
				return null;
			}
		});
	}

	public void update(T entity) {
		Assert.notNull(entity);

		hibernateTemplate.saveOrUpdate(entity);
	}

	public T findByIdentifier(PK id) {
		return (T) hibernateTemplate.get(entityClass, id);
	}
	
	public T findByFilter(PropertyFilter filter) {
		Assert.notNull(filter);

		List<T> resultList =  hibernateTemplate.findByCriteria(filter.getCriteria(), 0, 1);
		
		return resultList.size() > 0 ? resultList.get(0) : null;
	}

	public List<T> findAll() {
		DetachedCriteria criteria = DetachedCriteria.forClass(entityClass);
		return hibernateTemplate.findByCriteria(criteria);
	}

	public List<T> find(PropertyFilter filter) {
		Assert.notNull(filter);

		return hibernateTemplate.findByCriteria(filter.getCriteria());
	}


	public Page<T> findPage(Page<T> page) {
		return findPage(page, PropertyFilter.createFilter(entityClass));
	}

	public Page<T> findPage(final Page<T> page, final PropertyFilter filter) {

		int[] paginationData = paginate(page);

		List<T> records = hibernateTemplate.findByCriteria(
				filter.getCriteria(), paginationData[0], paginationData[1]);

		int totalRecords = ((Number) hibernateTemplate
				.execute(new HibernateCallback() {

					@Override
					public Object doInHibernate(Session session)
							throws HibernateException, SQLException {

						Object result = filter.getCriteria()
								.getExecutableCriteria(session).setProjection(
										Projections.rowCount()).uniqueResult();

						return ((Number) result);
					}
				})).intValue();

		page.setRecords(records);
		page.setTotalRecords(totalRecords);

		return page;
	}

	/**
	 * @param page
	 * @return int[] { firstResult, maxResults }
	 */
	private int[] paginate(Page<T> page) {
		return new int[] { (page.getCurrentPage() - 1) * page.getPageSize(),
				page.getCurrentPage() * page.getPageSize() };
	}

}
